<template>
  <component
    :is="wrapperComponent"
    :offset-top="offsetTop"
    :offset-bottom="offsetBottom"
    @on-change="handleAffixStateChange"
  >
    <div
      :class="`${prefix}-wrapper`"
      :style="wrapperStyle"
    >
      <div :class="`${prefix}`">
        <div :class="`${prefix}-ink`">
          <span
            v-show="showInk"
            :class="`${prefix}-ink-ball`"
            :style="{top: `${inkTop}px`}"
          />
        </div>
        <slot />
      </div>
    </div>
  </component>
</template>
<script>
import { scrollTop, findComponentsDownward, sharpMatcherRegx } from '../../utils/assist'
import { on, off } from '../../utils/dom'
export default {
  name: 'Anchor',
  provide () {
    return {
      anchorCom: this
    }
  },
  props: {
    affix: {
      type: Boolean,
      default: true
    },
    offsetTop: {
      type: Number,
      default: 0
    },
    offsetBottom: Number,
    bounds: {
      type: Number,
      default: 5
    },
    //        container: [String, HTMLElement],  // HTMLElement 在 SSR 下不支持
    container: null,
    showInk: {
      type: Boolean,
      default: false
    },
    scrollOffset: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      prefix: 'ivu-anchor',
      isAffixed: false, // current affixed state
      inkTop: 0,
      animating: false, // if is scrolling now
      currentLink: '', // current show link =>  #href -> currentLink = #href
      currentId: '', // current show title id =>  #href -> currentId = href
      scrollContainer: null,
      scrollElement: null,
      titlesOffsetArr: [],
      wrapperTop: 0,
      upperFirstTitle: true
    }
  },
  computed: {
    wrapperComponent () {
      return this.affix ? 'Affix' : 'div'
    },
    wrapperStyle () {
      return {
        maxHeight: this.offsetTop ? `calc(100vh - ${this.offsetTop}px)` : '100vh'
      }
    },
    containerIsWindow () {
      return this.scrollContainer === window
    }
  },
  watch: {
    '$route' () {
      this.handleHashChange()
      this.$nextTick(() => {
        this.handleScrollTo()
      })
    },
    container () {
      this.init()
    },
    currentLink (newHref, oldHref) {
      this.$emit('on-change', newHref, oldHref)
    }
  },
  mounted () {
    this.init()
  },
  methods: {
    handleAffixStateChange (state) {
      this.isAffixed = this.affix && state
    },
    handleScroll (e) {
      this.upperFirstTitle = e.target.scrollTop < this.titlesOffsetArr[0].offset
      if (this.animating) return
      this.updateTitleOffset()
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop || e.target.scrollTop
      this.getCurrentScrollAtTitleId(scrollTop)
    },
    handleHashChange () {
      const url = window.location.href
      const sharpLinkMatch = sharpMatcherRegx.exec(url)
      if (!sharpLinkMatch) return
      this.currentLink = sharpLinkMatch[0]
      this.currentId = sharpLinkMatch[1]
    },
    handleScrollTo () {
      const anchor = document.getElementById(this.currentId)
      const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`)
      let offset = this.scrollOffset
      if (currentLinkElementA) {
        offset = parseFloat(currentLinkElementA.getAttribute('data-scroll-offset'))
      }

      if (!anchor) return
      const offsetTop = anchor.offsetTop - this.wrapperTop - offset
      this.animating = true
      scrollTop(this.scrollContainer, this.scrollElement.scrollTop, offsetTop, 600, () => {
        this.animating = false
      })
      this.handleSetInkTop()
    },
    handleSetInkTop () {
      const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`)
      if (!currentLinkElementA) return
      const elementATop = currentLinkElementA.offsetTop
      const top = (elementATop < 0 ? this.offsetTop : elementATop)
      this.inkTop = top
    },
    updateTitleOffset () {
      const links = findComponentsDownward(this, 'AnchorLink').map(link => {
        return link.href
      })
      const idArr = links.map(link => {
        return link.split('#')[1]
      })
      const offsetArr = []
      idArr.forEach(id => {
        const titleEle = document.getElementById(id)
        if (titleEle) {
          offsetArr.push({
            link: `#${id}`,
            offset: titleEle.offsetTop - this.scrollElement.offsetTop
          })
        }
      })
      this.titlesOffsetArr = offsetArr
    },
    getCurrentScrollAtTitleId (scrollTop) {
      let i = -1
      const len = this.titlesOffsetArr.length
      let titleItem = {
        link: '#',
        offset: 0
      }
      scrollTop += this.bounds
      while (++i < len) {
        const currentEle = this.titlesOffsetArr[i]
        const nextEle = this.titlesOffsetArr[i + 1]
        if (scrollTop >= currentEle.offset && scrollTop < ((nextEle && nextEle.offset) || Infinity)) {
          titleItem = this.titlesOffsetArr[i]
          break
        }
      }
      this.currentLink = titleItem.link
      this.handleSetInkTop()
    },
    getContainer () {
      this.scrollContainer = this.container ? (typeof this.container === 'string' ? document.querySelector(this.container) : this.container) : window
      this.scrollElement = this.container ? this.scrollContainer : (document.documentElement || document.body)
    },
    removeListener () {
      off(this.scrollContainer, 'scroll', this.handleScroll)
      off(window, 'hashchange', this.handleHashChange)
    },
    init () {
      // const anchorLink = findComponentDownward(this, 'AnchorLink');
      this.handleHashChange()
      this.$nextTick(() => {
        this.removeListener()
        this.getContainer()
        this.wrapperTop = this.containerIsWindow ? 0 : this.scrollElement.offsetTop
        this.handleScrollTo()
        this.handleSetInkTop()
        this.updateTitleOffset()
        if (this.titlesOffsetArr[0]) {
          this.upperFirstTitle = this.scrollElement.scrollTop < this.titlesOffsetArr[0].offset
        }
        on(this.scrollContainer, 'scroll', this.handleScroll)
        on(window, 'hashchange', this.handleHashChange)
      })
    }
  },
  beforeUnmont () {
    this.removeListener()
  }
}
</script>
